﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ParallelRuntimeLibrary.Internal;
using System.Threading;

namespace ParallelRuntimeLibrary.Collections
{
    public class ConcurrentBag<T>
    {
        LightKeyValueList<PublicPrivateList<T>> mData;

        /*///////////////////////////////////////////////////////////////////////////////////////////////*/

        public ConcurrentBag()
        {
            mData = new LightKeyValueList<PublicPrivateList<T>>(NativeMethod.GetProcessorCount() + 2);
        }

        /*///////////////////////////////////////////////////////////////////////////////////////////////*/

        private PublicPrivateList<T> GetThreadList()
        {
            lock (mData)
            {
                PublicPrivateList<T> tmp;
                if (mData.TryGetValue(Thread.CurrentThread.ManagedThreadId, out tmp))
                    return tmp;
                else
                {
                    tmp = new PublicPrivateList<T>();
                    mData.Add(Thread.CurrentThread.ManagedThreadId, tmp);
                    return tmp;
                }
            }
        }

        private bool GetThreadList(out PublicPrivateList<T> list)
        {
            int threadId = Thread.CurrentThread.ManagedThreadId;
            lock (mData)
            {
                if (mData.TryGetValue(threadId, out list))
                    return true;
                else
                {
                    list = new PublicPrivateList<T>();
                    mData.Add(threadId, list);
                    return false;
                }
            }
        }

        /*///////////////////////////////////////////////////////////////////////////////////////////////*/

        #region Public - Private List

        public void Push(T value)
        {
            GetThreadList().PrivatePush(value);
        }

        public bool Pop(out T value)
        {
            // 1- First try to pop privately.
            PublicPrivateList<T> local;
            if (GetThreadList(out local))
                if (local.PrivatePop(out value)) return true;
            

            // 2- try to pop publicly
            lock (mData)
            {
                for (int i = 0; i < mData.mCount; i++)
                {
                    if (mData.mArray[i].Value.PublicPop(out value)) return true;
                }
                value = default(T);
                return false;
            }
        }

        public bool Pop(out T value, bool random)
        {
            // 1- First try to pop privately.
            PublicPrivateList<T> local;
            if (GetThreadList(out local))
                if (local.PrivatePop(out value)) return true;

            if (random)
            {
                lock (mData)
                {
                    for (int i = RandomEx.Next(mData.mCount - 1); i < mData.mCount; i++)
                    {
                        if (mData.mArray[i % mData.mCount].Value.PublicPop(out value)) return true;
                    }
                    value = default(T);
                    return false;
                }
            }
            else
            {
                lock (mData)
                {
                    for (int i = 0; i < mData.mCount; i++)
                    {
                        if (mData.mArray[i].Value.PublicPop(out value)) return true;
                    }
                    value = default(T);
                    return false;
                }  
            }
            

            // 2- try to pop publicly
            
        }

        public bool Pop(out T value, int millisecondsTimeout)
        {
            // 1- First try to pop privately.
            PublicPrivateList<T> local;
            if (GetThreadList(out local))
            {
                if (local.PrivatePop(out value)) return true;
            }

            // 2- try to pop publicly
            try
            {
                Monitor.TryEnter(mData);

                for (int i = 0; i < mData.mCount; i++)
                {
                    if (mData.mArray[i].Value.PublicPop(out value)) return true;
                }
                value = default(T);
                return false;
            }
            finally
            {
                Monitor.Exit(mData);
            }
        }

        #endregion

    }
}
